package org.finesys.common.feign.aop;

import feign.RetryableException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.finesys.common.feign.annotation.FeignRetry;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Slf4j
public class FeignRetryAspect {

    @Around("@annotation(org.finesys.common.feign.annotation.FeignRetry)")
    public Object retry(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        FeignRetry feignRetry = method.getAnnotation(FeignRetry.class);
        if (ObjectUtils.isEmpty(feignRetry)) {
            throw new NullPointerException("重试参数对象获取失败");
        }
        RetryTemplate retryTemplate = new RetryTemplate();
        retryTemplate.setBackOffPolicy(prepareBackOffPolicy(feignRetry));
        retryTemplate.setRetryPolicy(prepareRetryPolicy(feignRetry));
        return retryTemplate.execute(retryContext -> {
            int retryCount = retryContext.getRetryCount();
            log.info("Sending request method: {}, max attempt: {}, delay: {}, retryCount: {}", method.getName(),
                    feignRetry.maxAttempt(), feignRetry.backoff().delay(), retryCount);
            return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        });
    }

    private RetryPolicy prepareRetryPolicy(FeignRetry feignRetry) {
        Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<>();
        policyMap.put(RetryableException.class, true);
        for (Class<? extends Throwable> t : feignRetry.include()) {
            policyMap.put(t, true);
        }
        return new SimpleRetryPolicy(feignRetry.maxAttempt(), policyMap, true);
    }

    private BackOffPolicy prepareBackOffPolicy(FeignRetry feignRetry) {
        // 乘数
        double multiplier = feignRetry.backoff().multiplier();
        if (multiplier != 0) {
            ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
            exponentialBackOffPolicy.setMultiplier(multiplier);
            exponentialBackOffPolicy.setInitialInterval(feignRetry.backoff().delay());
            exponentialBackOffPolicy.setMaxInterval(feignRetry.backoff().maxDelay());

            return exponentialBackOffPolicy;
        }
        // 固定值
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(feignRetry.backoff().delay());
        return fixedBackOffPolicy;
    }

}
